package com.leyou.search.service;

import com.leyou.item.pojo.Brand;
import com.leyou.item.pojo.Category;
import com.leyou.item.pojo.SpecParam;
import com.leyou.search.clients.BrandClient;
import com.leyou.search.clients.CategoryClient;
import com.leyou.search.clients.SpecClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.utils.SearchRequest;
import com.leyou.search.utils.SearchResult;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SearchService {
    @Autowired
    private GoodsRepository repository;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private BrandClient brandClient;
    @Autowired
    private SpecClient specClient;
    private static final Logger logger = LoggerFactory.getLogger(SearchService.class);

    public SearchResult search(SearchRequest searchRequest) {
        //获取搜索的key
        String key = searchRequest.getKey();
        //获取分页信息
        Integer page = searchRequest.getPage() - 1;//spring-data 的分页page 从0开始
        Integer size = searchRequest.getSize();
        //判断key是否为空,为空返回null
        if (StringUtils.isBlank(key)) {
            return null;
        }
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //对结果进行筛选 通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));
        //规格参数过滤bool查询条件
        QueryBuilder query = buildBasicQueryWithFilter(searchRequest);
        queryBuilder.withQuery(query);//组合查询条件
        //分页条件
        queryBuilder.withPageable(PageRequest.of(page, size));
        //排序
        String sortBy = searchRequest.getSortBy();
        Boolean desc = searchRequest.getDescending();
        if (StringUtils.isNotBlank(sortBy)) {
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc ? SortOrder.DESC : SortOrder.ASC));
        }
        // 1.3、聚合
        String categoryAggName = "category"; // 商品分类聚合名称
        String brandAggName = "brand"; // 品牌聚合名称
        //添加分类和品牌的聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        //查询，获取结果
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) repository.search(queryBuilder.build());
        //解析分类聚合
        LongTerms categoryTerms = (LongTerms) goodsPage.getAggregation(categoryAggName);
        //获取分类聚合桶
        List<LongTerms.Bucket> categoryBuckets = categoryTerms.getBuckets();
        List<Long> cids = new ArrayList<>();
        categoryBuckets.forEach(bucket -> { //遍历桶获取cid
            Long cid = bucket.getKeyAsNumber().longValue();
            cids.add(cid);
        });
        //根据分类ID查询分类名称
        List<String> categoryNames = categoryClient.queryNameByIds(cids);
        List<Category> categories = new ArrayList<>(); //创建存放category的集合
        for (int i = 0; i < categoryNames.size(); i++) {
            Category c = new Category();
            c.setId(cids.get(i));
            c.setName(categoryNames.get(i));
            categories.add(c);
        }
        //解析brand聚合
        LongTerms brandTerms = (LongTerms) goodsPage.getAggregation(brandAggName);
        List<LongTerms.Bucket> brandBuckets = brandTerms.getBuckets();//获取桶
        List<Long> bids = new ArrayList<>();
        brandBuckets.forEach(bucket -> { //遍历桶,获取bid
            Long bid = bucket.getKeyAsNumber().longValue();
            bids.add(bid);
        });
        //根据品牌idList查询品牌
        List<Brand> brands = brandClient.queryBrandByIds(bids);

        // 判断商品分类数量，看是否需要对规格参数进行聚合
        List<Map<String, Object>> specs = null;
        if (categories.size() == 1) {
            // 如果分类只剩下一个，才进行规格参数过滤
            specs = getSpecs(categories.get(0).getId());
        }
        return new SearchResult(goodsPage.getTotalElements(), new Long(goodsPage.getTotalPages()), goodsPage.getContent(), categories, brands, specs);
    }

    /**
     * 聚合规格参数
     * 首先要根据分类查询所有的可搜索的规格参数，
     * <p>
     * 其次要对这些规格参数聚合，
     * <p>
     * 再次要把每一组聚合的结果封装到map中返回
     */
    private List<Map<String, Object>> getSpecs(Long cid) {
        try {
            // 根据分类查询规格
            List<SpecParam> params = this.specClient.querySpecParam(null, cid, true, null);

            // 创建集合，保存规格过滤条件
            List<Map<String, Object>> specs = new ArrayList<>();

            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

            // 聚合规格参数
            params.forEach(p -> {
                String key = p.getName();
                queryBuilder.addAggregation(AggregationBuilders.terms(key).field("specs." + key + ".keyword"));

            });

            // 查询
            Map<String, Aggregation> aggs = ((AggregatedPage<Goods>) this.repository.search(queryBuilder.build())).getAggregations().asMap();

            // 解析聚合结果
            params.forEach(param -> {
                Map<String, Object> spec = new HashMap<>();
                String key = param.getName();
                spec.put("k", key);
                StringTerms terms = (StringTerms) aggs.get(key);
                spec.put("options", terms.getBuckets().stream().map(StringTerms.Bucket::getKeyAsString));
                specs.add(spec);
            });

            return specs;
        } catch (Exception e) {
            logger.error("规格聚合出现异常：", e);
            return null;
        }
    }

    // 构建bool查询条件
    private QueryBuilder buildBasicQueryWithFilter(SearchRequest request) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 基本查询条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // 过滤条件构建器
        BoolQueryBuilder filterQueryBuilder = QueryBuilders.boolQuery();
        // 整理过滤条件
        Map<String, String> filter = request.getFilter();
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            // 商品分类和品牌已经聚合
            if (key != "cid3" && key != "brandId") {
                key = "specs." + key + ".keyword";
            }
            // 字符串类型，进行term查询
            filterQueryBuilder.must(QueryBuilders.termQuery(key, value));
        }
        // 添加过滤条件
        queryBuilder.filter(filterQueryBuilder);
        return queryBuilder;
    }
}
